package com.gitee.xzb617.mybatis.sqllog;

import cn.hutool.core.date.DateUtil;
import com.gitee.xzb617.mybatis.sqllog.context.ReqContext;
import com.gitee.xzb617.mybatis.sqllog.context.ReqContextHolder;
import com.gitee.xzb617.mybatis.sqllog.executor.SqlLogAsyncExecutor;
import com.gitee.xzb617.mybatis.sqllog.util.JsonUtil;
import com.gitee.xzb617.mybatis.sqllog.util.SqlUtil;
import com.gitee.xzb617.mybatis.sqllog.util.StackTraceUtil;
import com.gitee.xzb617.mybatis.sqllog.util.TypeUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Date;
import java.util.Properties;

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {
                MappedStatement.class, Object.class
        }),
        @Signature(type = Executor.class, method = "query", args = {
                MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
        })
})
public class SqlLogPlugin implements Interceptor {

    private final Logger logger = LoggerFactory.getLogger(SqlLogPlugin.class);

    private final SqlLogProperties sqlLogProperties;

    public SqlLogPlugin(SqlLogProperties sqlLogProperties) {
        this.sqlLogProperties = sqlLogProperties;
    }


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (!sqlLogProperties.getEnabled()) {
            return invocation.proceed();
        }

        // 计算耗时
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long finishTime = System.currentTimeMillis();

        int showResultCountWhen = sqlLogProperties.getShowResultCountWhen();
        boolean supportAsync = sqlLogProperties.getSupportAsync();

        // 获取调用栈
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        ArrayUtils.reverse(stackTrace);

        if (supportAsync) {
            // 异步执行
            SqlLogAsyncExecutor.instance()
                    .execute(() -> printSqlExecuteDetails(invocation, result, stackTrace, startTime, finishTime, showResultCountWhen));
        } else {
            // 非异步执行
            printSqlExecuteDetails(invocation, result, stackTrace, startTime, finishTime, showResultCountWhen);
        }

        return result;
    }

    /**
     * 打印sql执行详情
     * @param invocation
     * @param result
     * @param startTime
     * @param finishTime
     */
    private void printSqlExecuteDetails(Invocation invocation, Object result, StackTraceElement[] stackTrace, long startTime, long finishTime, int showResultCountWhen) {
        // 获取xml中的一个select/update/insert/delete节点，主要描述的是一条SQL语句
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = null;
        // 获取参数，if语句成立，表示sql语句有参数，参数格式是map形式
        if (invocation.getArgs().length > 1) {
            parameter = invocation.getArgs()[1];
        }
        // 获取到节点的id,即sql语句的id
        String sqlId = mappedStatement.getId();
        // BoundSql就是封装myBatis最终产生的sql类
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        // 获取节点的配置
        Configuration configuration = mappedStatement.getConfiguration();
        // 获取到最终的sql语句
        String sql = SqlUtil.formatSql(configuration, boundSql, false);


        ReqContext context = ReqContextHolder.get();
        if (context == null) {
            // 监听器、定时任务内部直接调用
            String requestUri = "[=== 监听器、定时任务内部直接调用 ===]";
            Date requestTime = new Date();

            if (StackTraceUtil.isInnerFunction(stackTrace, sqlLogProperties.getInnerInvokeMatchRules())) {
                // 过来调用栈
                String effectStackInfo = StackTraceUtil.filterEffectiveStackTraceElement(stackTrace, sqlLogProperties.getPackageMatchRules());
                StringBuilder builder = new StringBuilder();
                builder.append(" ").append("\n")
                        .append("------------------------------ SQL Execute Details Begin -------------------------------").append("\n")
                        .append("请求路径：").append(requestUri).append("\n")
                        .append("请求时间：").append(DateUtil.format(requestTime, "yyyy-MM-dd HH:mm:ss.SSS")).append("\n")
                        .append("业务调用栈(自上向下)：").append("\n\t")
                        .append(effectStackInfo).append("\n")
                        .append("Mapper方法:").append("\n\t")
                        .append(sqlId).append("\n")
                        .append("操作类型: 【").append(mappedStatement.getSqlCommandType()).append("】\n")
                        .append("Mapper入参:").append("\n\t")
                        .append(SqlUtil.formatArgs(configuration, boundSql)).append("\n")
                        .append("SQL语句:").append("\n\t")
                        .append(sql).append("\n")
                        .append("执行耗时：").append(finishTime - startTime).append("ms").append("\n")
                        .append("执行结果：").append("\n\t")
                        .append(analysisExecuteResult(result, showResultCountWhen)).append("\n")
                        .append("------------------------------ SQL Execute Details End --------------------------------").append("\n");

                // 最终打印
                System.out.println(builder.toString());
            }

        } else {
            // 外部访问
            String requestUri = context.getRequestUri();
            requestUri = requestUri.substring(1);

            // 过来调用栈
            String effectStackInfo = StackTraceUtil.filterEffectiveStackTraceElement(stackTrace, sqlLogProperties.getPackageMatchRules());
            StringBuilder builder = new StringBuilder();
            builder.append(" ").append("\n")
                    .append("------------------------------ SQL Execute Details Begin -------------------------------").append("\n")
                    .append("请求路径：").append(requestUri).append("\n")
                    .append("请求时间：").append(DateUtil.format(context.getRequestTime(), "yyyy-MM-dd HH:mm:ss.SSS")).append("\n")
                    .append("业务调用栈(自上向下)：").append("\n\t")
                    .append(effectStackInfo).append("\n")
                    .append("Mapper方法:").append("\n\t")
                    .append(sqlId).append("\n")
                    .append("操作类型: 【").append(mappedStatement.getSqlCommandType()).append("】\n")
                    .append("Mapper入参:").append("\n\t")
                    .append(SqlUtil.formatArgs(configuration, boundSql)).append("\n")
                    .append("SQL语句:").append("\n\t")
                    .append(sql).append("\n")
                    .append("执行耗时：").append(finishTime - startTime).append("ms").append("\n")
                    .append("执行结果：").append("\n\t")
                    .append(analysisExecuteResult(result, showResultCountWhen)).append("\n")
                    .append("------------------------------ SQL Execute Details End --------------------------------").append("\n");

            // 最终打印
            System.out.println(builder.toString());
        }

    }




    /**
     * 解析执行结果
     * @param result
     * @param showResultCountWhen
     * @return
     */
    private String analysisExecuteResult(Object result, int showResultCountWhen) {
        if (result == null) {
            return "[=== 无 ===]";
        }
        Class<?> aClass = result.getClass();
        StringBuilder builder = new StringBuilder();
        if (Collection.class.isAssignableFrom(aClass)) {
            Collection<Object> collection = (Collection) result;
            builder.append("[").append("\n\t");
            int i = 0;
            for (Object o : collection) {
                i++;
                if (i<=showResultCountWhen) {
                    if (o != null) {
                        builder.append(JsonUtil.toJson(o)).append("\n\t");
                    } else {
                        builder.append("NULL").append("\n\t");
                    }
                }
            }
            if (i > showResultCountWhen) {
                builder.append("=== 已忽略").append(i-showResultCountWhen).append("条数据，全部共").append(i).append("条数据 ===\n\t");
            }
            builder.append("]");
        }
        else if (TypeUtil.isObjectClass(aClass)) {
            builder.append(JsonUtil.toJson(result));
        } else {
            builder.append(result);
        }
        return builder.toString();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
